/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.boot.jaxb.hbm.transform;

import java.util.List;
import java.util.Locale;

import org.hibernate.boot.jaxb.hbm.spi.EntityInfo;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmDiscriminatorSubclassEntityType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmJoinedSubclassEntityType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmRootEntityType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmUnionSubclassEntityType;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEntityImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEntityMappingsImpl;
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.internal.util.collections.CollectionHelper;

/**
 * @author Steve Ebersole
 */
public class XmlPreprocessor {
	public static List<Binding<JaxbEntityMappingsImpl>> preprocessHbmXml(
			List<Binding<JaxbHbmHibernateMapping>> hbmXmlBindings,
			TransformationState transformationState) {
		final List<Binding<JaxbEntityMappingsImpl>> mappingBindings = CollectionHelper.arrayList( hbmXmlBindings.size() );
		hbmXmlBindings.forEach( (hbmXmlBinding) -> preProcessHbmXml( hbmXmlBinding, mappingBindings, transformationState ) );
		return mappingBindings;
	}

	private static void preProcessHbmXml(
			Binding<JaxbHbmHibernateMapping> hbmXmlBinding,
			List<Binding<JaxbEntityMappingsImpl>> mappingBindings,
			TransformationState transformationState) {
		final JaxbHbmHibernateMapping hbmRoot = hbmXmlBinding.getRoot();
		final JaxbEntityMappingsImpl mappingRoot = new JaxbEntityMappingsImpl();
		transformationState.getJaxbRootMap().put( hbmRoot, mappingRoot );

		mappingRoot.setDescription( String.format(
				Locale.ROOT,
				"Generated by Hibernate HbmXmlTransformer from %s (%s)",
				hbmXmlBinding.getOrigin().getName(),
				hbmXmlBinding.getOrigin().getType()
		) );
		mappingBindings.add( new Binding<>( mappingRoot, hbmXmlBinding.getOrigin() ) );

		hbmRoot.getTypedef().forEach( hbmTypeDef -> {
			transformationState.getTypeDefMap().put( hbmTypeDef.getName(), hbmTypeDef );
		} );

		hbmRoot.getClazz().forEach( (hbmRootEntity) -> {
			preProcessRooEntity( hbmRootEntity, hbmRoot, mappingRoot, transformationState );
		} );

		hbmRoot.getSubclass().forEach( (hbmSubclass) -> {
			preProcessSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );

		hbmRoot.getJoinedSubclass().forEach( (hbmSubclass) -> {
			preProcessJoinedSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );

		hbmRoot.getUnionSubclass().forEach( (hbmSubclass) -> {
			preProcessUnionSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );
	}

	private static void preProcessRooEntity(
			JaxbHbmRootEntityType hbmRootEntity,
			JaxbHbmHibernateMapping hbmRoot,
			JaxbEntityMappingsImpl mappingRoot,
			TransformationState transformationState) {
		final JaxbEntityImpl mappingEntity = new JaxbEntityImpl();
		mappingRoot.getEntities().add( mappingEntity );

		commonEntityPreprocessing( hbmRootEntity, hbmRoot, mappingRoot, transformationState, mappingEntity );

		hbmRootEntity.getSubclass().forEach( (hbmSubclass) -> {
			preProcessSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );

		hbmRootEntity.getJoinedSubclass().forEach( (hbmSubclass) -> {
			preProcessJoinedSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );

		hbmRootEntity.getUnionSubclass().forEach( (hbmSubclass) -> {
			preProcessUnionSubclass( hbmSubclass, hbmRoot, mappingRoot, transformationState );
		} );
	}

	private static void commonEntityPreprocessing(
			EntityInfo hbmEntity,
			JaxbHbmHibernateMapping hbmRoot,
			JaxbEntityMappingsImpl mappingRoot,
			TransformationState transformationState,
			JaxbEntityImpl mappingEntity) {
		// apply some basic info to the mapping.xsd entity
		TransformationHelper.transfer( hbmEntity::getEntityName, mappingEntity::setName );
		TransformationHelper.transfer( hbmEntity::getName, mappingEntity::setClazz );

		final String entityName = TransformationHelper.determineEntityName( hbmEntity, hbmRoot );
		transformationState.getEntityToHbmXmlMap().put( entityName, hbmRoot );
		transformationState.getEntityToMappingXmlMap().put( entityName, mappingRoot );
		transformationState.getHbmEntityByName().put( entityName, hbmEntity );
		transformationState.getMappingEntityByName().put( entityName, mappingEntity );

		// todo (7.0) : walk attributes looking for components
	}

	private static void preProcessSubclass(
			JaxbHbmDiscriminatorSubclassEntityType hbmSubclass,
			JaxbHbmHibernateMapping hbmRoot,
			JaxbEntityMappingsImpl mappingRoot,
			TransformationState transformationState) {
		final JaxbEntityImpl mappingEntity = new JaxbEntityImpl();
		mappingRoot.getEntities().add( mappingEntity );

		commonEntityPreprocessing( hbmSubclass, hbmRoot, mappingRoot, transformationState, mappingEntity );

		hbmSubclass.getSubclass().forEach( (hbmSubclassSubclass) -> {
			preProcessSubclass( hbmSubclassSubclass, hbmRoot, mappingRoot, transformationState );
		} );
	}

	private static void preProcessJoinedSubclass(
			JaxbHbmJoinedSubclassEntityType hbmSubclass,
			JaxbHbmHibernateMapping hbmRoot,
			JaxbEntityMappingsImpl mappingRoot,
			TransformationState transformationState) {
		final JaxbEntityImpl mappingEntity = new JaxbEntityImpl();
		mappingRoot.getEntities().add( mappingEntity );

		commonEntityPreprocessing( hbmSubclass, hbmRoot, mappingRoot, transformationState, mappingEntity );

		hbmSubclass.getJoinedSubclass().forEach( (hbmSubclassSubclass) -> {
			preProcessJoinedSubclass( hbmSubclassSubclass, hbmRoot, mappingRoot, transformationState );
		} );
	}

	private static void preProcessUnionSubclass(
			JaxbHbmUnionSubclassEntityType hbmSubclass,
			JaxbHbmHibernateMapping hbmRoot,
			JaxbEntityMappingsImpl mappingRoot,
			TransformationState transformationState) {
		final JaxbEntityImpl mappingEntity = new JaxbEntityImpl();
		mappingRoot.getEntities().add( mappingEntity );

		commonEntityPreprocessing( hbmSubclass, hbmRoot, mappingRoot, transformationState, mappingEntity );

		hbmSubclass.getUnionSubclass().forEach( (hbmSubclassSubclass) -> {
			preProcessUnionSubclass( hbmSubclassSubclass, hbmRoot, mappingRoot, transformationState );
		} );
	}
}
